package cn.sylinx.horm.core;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import cn.sylinx.horm.config.OrmConfigHolder;
import cn.sylinx.horm.core.common.Callable;
import cn.sylinx.horm.core.common.PreparedCallWrapper;
import cn.sylinx.horm.core.common.PreparedCallable;
import cn.sylinx.horm.core.common.TypedParameter;
import cn.sylinx.horm.core.datasource.ConnectionProvider;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.util.DbKit;
import cn.sylinx.horm.util.GLog;

/**
 * sql client
 *
 * @author johnhan
 */
abstract class AbstractSqlClient implements SqlClient {

    protected ConnectionProvider connectionProvider;

    public ConnectionProvider getConnectionProvider() {
        return connectionProvider;
    }

    public void setConnectionProvider(ConnectionProvider connectionProvider) {
        this.connectionProvider = connectionProvider;
    }

    public String getDataSourceName() {
        return connectionProvider.getDataSourceName();
    }

    @Override
    public void close() {
        connectionProvider.close();
    }

    public int insert(final String insertSql, final Object... params) throws SQLException {
        return update(insertSql, params);
    }

    @SuppressWarnings("unchecked")
    @Override
    public int[] insertBatch(String insertSqlTemplateInput, List<Object[]> batchParamsInput) throws SQLException {

        if (batchParamsInput == null || batchParamsInput.isEmpty()) {
            throw new HORMException("批量插入参数丢失");
        }

        return call((d, c, s, p) -> {

            PreparedStatement pst = null;

            try {

                List<Object[]> batchParamsInner = (List<Object[]>) p[0];
                pst = c.prepareStatement(s);
                // 设置参数
                for (Object[] row : batchParamsInner) {
                    setParameters(pst, row);
                    pst.addBatch();
                }
                return pst.executeBatch();

            } finally {
                DbKit.closeQuietly(pst);
            }
        }, insertSqlTemplateInput, new Object[]{batchParamsInput});
    }

    public int delete(final String deleteSql, final Object... params) throws SQLException {
        return update(deleteSql, params);
    }

    public int update(final String updateSqlInput, final Object... paramsInput) throws SQLException {
        return call((d, c, s, p) -> {
            PreparedStatement pst = null;
            try {
                pst = c.prepareStatement(s);
                // 设置参数
                setParameters(pst, p);
                return pst.executeUpdate();
            } finally {
                DbKit.closeQuietly(pst);
            }
        }, updateSqlInput, paramsInput);
    }

    public Object insertForRetrieval(final String insertSqlInput, final Object... paramsInput) throws SQLException {
        return call((d, c, s, p) -> {
            ResultSet rs = null;
            PreparedStatement pst = null;
            try {
                pst = c.prepareStatement(s, PreparedStatement.RETURN_GENERATED_KEYS);
                // 设置参数
                setParameters(pst, p);
                int result = pst.executeUpdate();
                if (result < 1) {
                    GLog.debug("0 rows inserted, sql:{}, params:{}", s, p);
                }
                rs = pst.getGeneratedKeys();
                return rs.next() ? rs.getObject(1) : null;
            } finally {
                DbKit.closeQuietly(rs, pst);
            }
        }, insertSqlInput, paramsInput);
    }

    public boolean execute(final String exeSqlInput, final Object... paramsInput) throws SQLException {
        return call((d, c, s, p) -> {
            PreparedStatement pst = null;
            try {
                pst = c.prepareStatement(s);
                if (p != null) {
                    setParameters(pst, p);
                }
                return pst.execute();
            } finally {
                DbKit.closeQuietly(pst);
            }
        }, exeSqlInput, paramsInput);
    }

    public List<Object[]> query(final String querySql, final Object... params) throws SQLException {
        return queryFromDB(querySql, params);
    }

    private List<Object[]> queryFromDB(final String querySqlInput, final Object... paramsInput) throws SQLException {
        return call((d, c, s, p) -> {
            ResultSet rs;
            PreparedStatement pst = null;
            try {
                List<Object[]> result = new ArrayList<>();
                pst = c.prepareStatement(s);
                // 设置参数
                setParameters(pst, p);
                rs = pst.executeQuery();
                ResultSetMetaData rsmetas = rs.getMetaData();
                int columnCount = rsmetas.getColumnCount();
                while (rs.next()) {
                    Object[] row = new Object[columnCount];
                    for (int i = 1, len = columnCount + 1; i < len; ++i) {
                        row[i - 1] = rs.getObject(i);
                    }
                    result.add(row);
                }
                return result;
            } finally {
                DbKit.closeQuietly(pst);
            }
        }, querySqlInput, paramsInput);
    }

    public List<Object> queryForSingleColumn(final String querySql, final Object... params) throws SQLException {
        return queryForSingleColumnFromDB(querySql, params);
    }

    private List<Object> queryForSingleColumnFromDB(final String querySqlInput, final Object... paramsInput)
            throws SQLException {
        return call((d, c, s, p) -> {
            ResultSet rs;
            PreparedStatement pst = null;
            try {
                List<Object> result = new ArrayList<>();
                pst = c.prepareStatement(s);
                // 设置参数
                setParameters(pst, p);
                rs = pst.executeQuery();
                while (rs.next()) {
                    result.add(rs.getObject(1));
                }
                return result;
            } finally {
                DbKit.closeQuietly(pst);
            }
        }, querySqlInput, paramsInput);
    }

    public <T> T call(Callable<T> callable) throws SQLException {
        Connection conn = connectionProvider.getConnection();
        try {
            return callable.call(conn);
        } finally {
            connectionProvider.releaseConnection(conn);
        }
    }

    protected <T> T call(PreparedCallable<T> preparedCallable, String sql, Object[] params) throws SQLException {

        String datasourceName = connectionProvider.getDataSourceName();
        Connection conn = connectionProvider.getConnection();
        PreparedCallWrapper callWrapper;
        PreparedCallable<T> callableDelegate;

        try {
            callWrapper = PreparedCallWrapperManager.getCallWrapper();
            callableDelegate = callWrapper == null ? preparedCallable : callWrapper.wrap(preparedCallable);
            long currentTime = System.currentTimeMillis();
            T t = callableDelegate.call(datasourceName, conn, sql, params);
            if (OrmConfigHolder.isPrintSqlUseTime()) {
                // 打印sql用时
                GLog.info("SQL EXECUTE TIME: {}, {}", System.currentTimeMillis() - currentTime, SqlPrettyHelper.output(sql, params));
            }
            return t;

        } finally {
            connectionProvider.releaseConnection(conn);

            // earliy gc
            callableDelegate = null;
            callWrapper = null;
            conn = null;
            datasourceName = null;
        }
    }

    protected void setParameters(final PreparedStatement pst, final Object... params) throws SQLException {
        if (params != null) {
            for (int i = 0, size = params.length; i < size; ++i) {

                if (params[i] instanceof TypedParameter) {
                    pst.setObject(i + 1, ((TypedParameter) params[i]).getParameter());
                } else {
                    pst.setObject(i + 1, params[i]);
                }
            }
        }
    }
}